package org.mv.mm.api;

import java.io.Serializable;
import java.util.*;

/**
 *
 * @author Martin
 */
public class Fragment implements Serializable, IConnectible {

  private static Map<String, Fragment> fragmentNameMap = new HashMap<>(300);
  static final long serialVersionUID = 10120330L;

  /**
   * Factory method to create a new Fragment instance. This is supposed for
   * creating new Fragment by the user.
   *
   * It calls other methods for generating unique id and adding into static map.
   *
   * @param workName
   * @param parentFrag
   * @return created Fragment instance
   */
  public static Fragment createFragment(Doc doc, String workName, Fragment parentFrag) {
    Fragment newFrag = new Fragment(doc, workName, parentFrag);

    // Automatically add this fragment as a child to the parent if set.
    if (parentFrag != null) {
      parentFrag.addChildFragment(newFrag);
    }

    // Add it to the static map.
    if (!addToNameMap(newFrag)) {
      //TODO ?
      return null;
    }
    return newFrag;
  }

  /**
   * Return all instances of Fragment.
   *
   * @return all fragments
   */
  public static List<Fragment> getFragments() {
    List<Fragment> frags = new ArrayList<>(fragmentNameMap.values());
    return frags;
  }

  public static List<Fragment> getFragmentsWithoutParent() {
    List<Fragment> fragments = new ArrayList<>();
    for (Fragment frag : fragmentNameMap.values()) {
      if (frag.getParentFragment() == null) {
        fragments.add(frag);
      }
    }
    return fragments;
  }

  public static void removeFragment(Fragment fragmentToDelete) {
    fragmentNameMap.remove(fragmentToDelete.workName);

    // Remove reference from parent
    if (fragmentToDelete.parentFragment != null) {
      fragmentToDelete.parentFragment.childrenFragments.remove(fragmentToDelete);
    }
    // Remove references from children to this as parent
    if (fragmentToDelete.childrenFragments != null) {
      for (Fragment frag : fragmentToDelete.childrenFragments) {
        frag.parentFragment = null;
        removeFragment(frag); //TODO kaskáda (přiřadit k nějaké té uni Connection
      }
    }

    // Remove fragment from the document
    if (fragmentToDelete.doc != null) {
      fragmentToDelete.doc.removeFragment(fragmentToDelete);
    }
  }

  /**
   * Returns the fragment map (used for serialization).
   *
   * @return the map of fragments
   */
  public static Map<String, Fragment> getFragmentNameMap() {
    return fragmentNameMap;
  }

  public static void setFragmentNameMap(Map<String, Fragment> fragmentNameMap) {
    Fragment.fragmentNameMap = fragmentNameMap;

  }
  private String abbrev;
  private String workName;
  private String description;
  private String author;
  private String title;
  private int position;
  private String fragmentText;
  private Date versionDate;
  private Fragment parentFragment;
  private Doc doc;
  private List<Fragment> childrenFragments = new ArrayList<>();

  /**
   * Private constructor.
   *
   * @param workName
   * @param parentChap
   */
  private Fragment(Doc doc, String workName, Fragment parentChap) {

    this.doc = doc;
    this.workName = workName;
    this.abbrev = workName.substring(0, 1).toUpperCase();
    this.parentFragment = parentChap;
  }

  /**
   * Private constructor.
   *
   * @param workName
   * @param description
   * @param author
   * @param title
   * @param position
   * @param fragmentText
   * @param versionDate
   * @param parentChap
   */
  private Fragment(String workName, String description, String author,
          String title, int position, String fragmentText, Date versionDate, Fragment parentChap) {

    this.workName = workName;
    this.description = description;
    this.author = author;
    this.title = title;
    this.position = position;
    this.fragmentText = fragmentText;
    this.versionDate = versionDate;
    this.parentFragment = parentChap;

  }

  public String getAbbrev() {
    return abbrev;
  }

  public void setAbbrev(String abbrev) {
    this.abbrev = abbrev;
  }

  public String getAuthor() {
    return author;
  }

  public void setAuthor(String author) {
    this.author = author;
  }

  public List<Fragment> getChildrenFragments() {
    return childrenFragments;
  }

  public void addChildFragment(Fragment child) {
    this.childrenFragments.add(child);
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public String getFragmentText() {
    return fragmentText;
  }

  public void setFragmentText(String fragmentText) {
    this.fragmentText = fragmentText;
  }

  public Fragment getParentFragment() {
    return parentFragment;
  }

  public void setParentFragment(Fragment parentFragment) {
    this.parentFragment = parentFragment;
  }

  public int getPosition() {
    return position;
  }

  public void setPosition(int position) {
    this.position = position;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public Date getVersionDate() {
    return versionDate;
  }

  public void setVersionDate(Date versionDate) {
    this.versionDate = versionDate;
  }

  public String getWorkName() {
    return workName;
  }

  public void setWorkName(String workName) {
    this.workName = workName;
  }

  public Doc getDoc() {
    return doc;
  }

  public void removeDoc() {
    this.doc = null;
  }

  /**
   * ********************************
   * == PRIVATE INSTANCE METHODS == 
   ******************************
   */
  /**
   * Adds provided Fragment instance to the static map under the workName as a
   * key.
   *
   * @param frag Fragment to save into map
   * @return true if the id was unique and the fragment instance has been saved
   * into map
   */
  private static boolean addToNameMap(Fragment frag) {

    if (fragmentNameMap.containsKey(frag.workName)) {
      return false;
    } else {
      fragmentNameMap.put(frag.workName, frag);
      return true;
    }

  }
}
